/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.web.context;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.core.log.LogMessage;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

/**
 *
 * 在每次请求处理之前，将该请求相关的安全上下文信息加载到 SecurityContextHolder 中，
 * 然后在该次请求处理完成之后，将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中，
 * 然后将 SecurityContextHolder 中的信息清除，例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
 *
 *
 * 在请求处理之前，将从 {@link SecurityContextRepository} 获取的 SecurityContext 加载到 {@link SecurityContextHolder}
 * 在请求处理完成后，将这次请求的信息储存回“仓库”里面，默认使用的是 {@link HttpSessionSecurityContextRepository}
 * 相关信息参考 <tt> HttpSession </ tt> 相关的配置选项。
 *
 * <p>
 * 该过滤器每个请求仅执行一次，以解决 servlet 容器（特别是Weblogic）的不兼容性。
 * <p>
 *
 * 必须在任何身份验证处理机制之前执行此过滤器。
 * 身份验证处理机制（例如BASIC，CAS处理过滤器等）期望 SecurityContextHolder 在执行时包含有效的 SecurityContext
 * <p>
 *
 * 这实质上是对旧的 HttpSessionContextIntegrationFilter 的重构，以将存储问题委派给单独的策略，允许在请求之间维护安全上下文的方式中进行更多自定义。
 * <p>
 *
 *  forceEagerSessionCreation 属性可用于确保会话始终在过滤器链执行之前可用（默认值为 false，因为这会占用大量资源，因此不建议这样做）。
 *
 */
public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";

	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
		// 确保每个请求仅应用一次过滤器
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}

		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}

		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);

		try {
			// 将从 SecurityContextRepository 获取的 SecurityContext 加载到 SecurityContextHolder
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}

		finally {
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// 请求结束后，删除 SecurityContextHolder 内容。
			SecurityContextHolder.clearContext();
			// 将这次请求的信息保存会“仓库"里面
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}

}
